<?php

/**
  * SquirrelMail Yubikey Plugin
  *
  * @copyright &copy; 2009 Richard Ferguson <yubikey@fergusnet.com>
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @package plugins
  * @subpackage yubikey
  */

function yubikey_init()
{
    if (!@include_once (SM_PATH . 'config/config_yubikey.php'))
        if (!@include_once (SM_PATH . 'plugins/yubikey/config.php')) {
            sq_change_text_domain('yubikey');
            print( _("ERROR: Can not find Yubikey plugin configuration file!") );
            sq_change_text_domain('squirrelmail');
            return FALSE;
        }

    global $yubikey_input_mode;
    if ($yubikey_input_mode == 1) {
        /* make sure password_forget is loaded before yubikey */
        global $plugins;
        $my_index = array_search('yubikey', $plugins);
        $pf_index = array_search('password_forget', $plugins);
        if ($pf_index && $pf_index > $my_index) {
            sq_change_text_domain('yubikey');
            print( _("ERROR: The password_forget plugin must come BEFORE yubikey in the plugins array!  Run ./conf.pl to reorder plugins.") );
            sq_change_text_domain('squirrelmail');
            return FALSE;
        }
    } 
    return TRUE;
}

/* insert options into personal preferences */
function yubikey_options_do()
{
    global $optpage_data;

    /* get saved information */
    global $username, $data_dir;
    $require_yubikey =  getPref($data_dir,$username,'yubikey_required','No');
    $yubikey_id = getPref($data_dir,$username,'yubikey_id');
    $yubikey_id_backup = getPref($data_dir,$username,'yubikey_id_backup');
    
    sq_change_text_domain('yubikey');
    $optpage_data['grps']['yubikey'] = _("Yubikey Options");
    $optionValues = array();
    $optionValues[] = array(
        'name'    => 'yubikey_required',
        'caption' => _("Require Yubikey for Authentication"),
        'type'    => SMOPT_TYPE_BOOLEAN,
        'refresh' => SMOPT_REFRESH_NONE,
        'initial_value' => $require_yubikey
    );
    $optionValues[] = array(
        'name'    => 'yubikey_id',
        'caption' => _("Primary Yubikey ID"),
        'type'    => SMOPT_TYPE_STRING,
        'refresh' => SMOPT_REFRESH_NONE,
        'initial_value' => $yubikey_id,
        'save' => 'yubikey_save_id'
    );
    $optionValues[] = array(
        'name'    => 'yubikey_id_backup',
        'caption' => _("Backup Yubikey ID"),
        'type'    => SMOPT_TYPE_STRING,
        'refresh' => SMOPT_REFRESH_NONE,
        'initial_value' => $yubikey_id_backup,
        'save' => 'yubikey_save_id'
    );
    $optpage_data['vals']['yubikey'] = $optionValues;
    sq_change_text_domain('squirrelmail');
}

/* truncate yubikey id to 12 chars */
function yubikey_save_id($option) 
{
    @include_once (SM_PATH . 'plugins/yubikey/Yubikey.php');
    if (!yubikey_init()) 
        return;

    global $yubico_server_id, $yubico_server_key,$yubico_server_url;
    
    if (!yubikey_verify_modhex($option->new_value)) {
        /* id is not modhex */
        include_once(SM_PATH . 'functions/display_messages.php' );
        sq_change_text_domain('yubikey');
        error_option_save( _("Yubikey ID not saved! Entered Yubikey ID is not modhex.") );
        sq_change_text_domain('squirrelmail');
        return;
    }

    if (strlen($option->new_value) > 12) {
        /* if the user entered a full yubikey otp we
         * must authenticate it so it can't be used */
        if (strlen($option->new_value) == 44) {
            $yubi = new Yubikey($yubico_server_id);
            if ($yubico_server_key) {
                $yubi->setKey($yubico_server_key);
            }
            if ($yubico_server_url) {
                $yubi->setUrl($yubico_server_url);
            }
            $yubi->setCurlTimeout(10);
            $yubi->setTimestampTolerance(600);
            if (!$yubi->verify($option->new_value)) {
                /* verification failed! Don't save ID */
                include_once(SM_PATH . 'functions/display_messages.php' );
                sq_change_text_domain('yubikey');
                error_option_save( _("Yubikey ID not saved! Entered Yubikey OTP failed authentication.") );
                sq_change_text_domain('squirrelmail');
                return;
            }
        }
        $option->new_value = substr($option->new_value, 0, 12);
    }
    save_option($option);
}

/* add yubikey input box */
function yubikey_form_do()
{
    if (!yubikey_init())
        return;

    global $yubikey_input_mode;
    if ($yubikey_input_mode != 2) 
        return;

    sq_change_text_domain('yubikey');

    echo html_tag( 'table',
        html_tag( 'tr', 
        html_tag( 'td', _("Yubikey OTP:"), 'right', '', 'width="30%"' ) . 
        html_tag( 'td', 
            addInput('yubikeyotp', null, 0, 0, ' onfocus="alreadyFocused=true;"'),
            'left', '', 'width="70%"' )
        ), 'center','', 'width="350"');

    sq_change_text_domain('squirrelmail');
}

/* process yubikey otp from login form */
function yubikey_login_do()
{
    if (!yubikey_init())
        return;

    global $yubikey_input_mode;
    if ($yubikey_input_mode != 1) 
        return;

    global $secretkey;

    /* we need at least 44 modhex characters for an OTP,
     * but if we find 64 or more, assume its a Yubikey
     * in static password mode.
     */
    $mhcnt = yubikey_count_modhex($secretkey);
    if ($mhcnt >= 44 and $mhcnt < 64) {
        $yubikey_otp = substr($secretkey, strlen($secretkey)-44, 44);
        $secretkey = substr($secretkey, 0, strlen($secretkey)-44);
        sqsession_register($yubikey_otp, 'yubikeyotp');
    }
}

/* verify yubikey one time password */
function yubikey_verify_do()
{    
    global $username, $data_dir;
    global $yubikey_disguise_errors;

    if (!yubikey_init())
        return;

    $require_yubikey =  getPref($data_dir,$username,'yubikey_required',0);
    $yubikey_id = getPref($data_dir,$username,'yubikey_id',0);
    $yubikey_id_backup = getPref($data_dir,$username,'yubikey_id_backup',0);

    if ($require_yubikey && !empty($yubikey_id)) {
        /* user preference is requesting yubikey auth */
        sqGetGlobalVar('yubikeyotp',$yubikey_otp);
        if (!yubikey_authenticate($yubikey_otp, $yubikey_id, $yubikey_id_backup)) {
            /* yubikey authentication failed */
            if ($yubikey_disguise_errors) {
                yubikey_error( _("Unknown user or password incorrect.") );
            } else {
                sq_change_text_domain('yubikey');
                yubikey_error( _("Yubikey OTP authentication failed.") );
            }
        }
    }
}

/* communicate with Yubico's authentication service */
function yubikey_authenticate($otp, $id, $idbak)
{
    @include_once (SM_PATH . 'plugins/yubikey/Yubikey.php');
    if (!yubikey_init())
        return;

    global $yubico_server_id, $yubico_server_key,$yubico_server_url;

    /* verify otp matches stored id */
    if (substr($otp, 0, 12) != substr($id, 0, 12)) {
        /* we don't match the primary id */
        if (!empty($idbak) && substr($otp, 0, 12) != substr($idbak, 0 , 12)) {
            /* we don't match the backup id */
            return false;
        }
    }

    /* authenticate against Yubico's servers */
    $yubi = new Yubikey($yubico_server_id);
    if ($yubico_server_key) {
        if(!$yubi->setKey($yubico_server_key)) {
            sq_change_text_domain('yubikey');
            yubikey_error( _("Error setting yubico_server_key.") );
        }
    }
    if ($yubico_server_url) {
        if(!$yubi->setUrl($yubico_server_url)) {
            sq_change_text_domain('yubikey');
            yubikey_error( _("Error setting yubico_server_url.") );
        }
    }
    $yubi->setCurlTimeout(10);
    $yubi->setTimestampTolerance(600);
    return $yubi->verify($otp);
}

/* count modhex chars at end of string */
function yubikey_count_modhex($str)
{
    $cnt = 0;
    for ($i=strlen($str)-1;$i>=0;$i--) {
        /* cbde fghi jkln rtuv */
        $c = substr($str,$i,1);
        if ($c == 'c' or $c == 'b' or
            $c == 'd' or $c == 'e' or
            $c == 'f' or $c == 'g' or
            $c == 'h' or $c == 'i' or
            $c == 'j' or $c == 'k' or
            $c == 'l' or $c == 'n' or
            $c == 'r' or $c == 't' or
            $c == 'u' or $c == 'v') {
                $cnt++;
        }
    }    
    return $cnt;
}

/* verify a string is modhex only */
function yubikey_verify_modhex($str)
{
    for ($i=0;$i<strlen($str);$i++) {
        /* cbde fghi jkln rtuv */
        $c = substr($str,$i,1);
        if (!($c == 'c' or $c == 'b' or
              $c == 'd' or $c == 'e' or
              $c == 'f' or $c == 'g' or
              $c == 'h' or $c == 'i' or
              $c == 'j' or $c == 'k' or
              $c == 'l' or $c == 'n' or
              $c == 'r' or $c == 't' or
              $c == 'u' or $c == 'v')) {
            return 0;
        }
    }    
    return 1;
}

/* display error message and close session */
function yubikey_error($msg)
{
    include_once(SM_PATH . 'functions/display_messages.php' );
    sqsession_destroy();
    /* terminate the session nicely */
    logout_error($msg);
    exit;
}

// vim: ts=4: expandtab
